﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using Ext.Net;
using Ext.Net.Utilities;


namespace KamLib
{
    public class WhereArgSortField
    {
        public string Name;
        public SortDirection Dir;
    }

    public class WhereArgSortFields : List<WhereArgSortField>
    {
        public void Add(string name, SortDirection dir = SortDirection.ASC)
        {
            Add(new WhereArgSortField { Name = name, Dir = dir });
        }
        public void Insert(int index, string name, SortDirection dir = SortDirection.ASC)
        {
            Insert(index, new WhereArgSortField { Name = name, Dir = dir });
        }

        public string ToSql(string fieldPrefix = null, string quote = null)
        {
            var sb = new StringBuilder();
            foreach (var item in this)
            {
                if (sb.Length > 0) sb.Append(", ");
                sb.Append(fieldPrefix);
                sb.Append(quote);
                sb.Append(item.Name);
                sb.Append(quote);
                if (item.Dir == SortDirection.DESC)
                    sb.Append(" desc");
            }
            return sb.ToString();
        }
    }

    public class WhereArgs
    {
        public readonly UilContext Context;

        public int Start = -1;
        public int Limit = -1;
        public int Total;
        /// <summary>
        /// Показывает, ожидается ли, что будет установлено значение Total
        /// </summary>
        public bool TotalExpected;

        public Dictionary<string, object> KeyValues;

        public WhereArgSortFields SortFields = new WhereArgSortFields();
        public bool UseOrderAsThen;

        public List<Filter> Filters;
        public bool UseFilters = true;

        public class Filter
        {
            public string Name;
            public object Value;
            public FilterType FilterType;
            public Comparison Comparison = Comparison.Eq;
            public bool Applied;

            public Filter(FilterType filterType, string name, object value, Comparison comparison = Comparison.Eq)
            {
                FilterType = filterType;
                Name = name;
                Value = value;
                Comparison = comparison;
            }
            public Filter(string name, object value, Comparison comparison = Comparison.Eq)
            {
                Name = name;
                Value = value;
                Comparison = comparison;
                if (value == null)
                    FilterType = FilterType.String;
                else
                {
                    var t = value.GetType();
                    FilterType =
                        t == typeof(bool) || t == typeof(bool?) ? FilterType.Boolean :
                        t == typeof(DateTime) || t == typeof(DateTime?) ? FilterType.Date :
                        t == typeof(ReadOnlyCollection<string>) ? FilterType.List :
                        t == typeof(int) || t == typeof(int?) || t == typeof(Int64) || t == typeof(Int64) || t == typeof(double) || t == typeof(double?) ? FilterType.Numeric :
                        FilterType.String;
                }
            }
        }

        public static List<Filter> NewFilters(string sfilters)
        {
            if (sfilters.IsEmpty()) return null;
            return new FilterConditions(sfilters).Conditions.Select(a =>
                new Filter(a.FilterType, a.Name, a.ValueAsObject(), a.Comparison)
            ).ToList();
        }

        public WhereArgs(UilContext context) { Context = context; }
        public WhereArgs(UilContext context, Dictionary<string, object> key)
        {
            Context = context;
            if (key != null)
                KeyValues = key;
        }
        public WhereArgs(UilContext context, UiKey key)
        {
            Context = context;
            if (key != null)
                KeyValues = key.ID;
        }

        public void MakeAppliedFilters(params string[] fields)
        {
            if (Filters == null || Filters.Count == 0) return;
            foreach (var field in fields)
            {
                var f = Filters.FirstOrDefault(a => a.Name == field);
                if (f != null)
                    f.Applied = true;
            }
        }

        #region Notifications

        public void ShowInfo(string Message, string Title = null, bool? Pinned = null, int? Width = null, int? Height = null)
        {
            Context.Notifications.AddInfo(Message, Title, Pinned, Width, Height);
        }

        public void ShowError(string Message, string Title = null, bool? Pinned = null, int? Width = null, int? Height = null)
        {
            Context.Notifications.AddError(Message, Title, Pinned, Width, Height);
        }

        public void ShowWarning(string Message, string Title = null, bool? Pinned = null, int? Width = null, int? Height = null)
        {
            Context.Notifications.AddWarning(Message, Title, Pinned, Width, Height);
        }

        #endregion

    }

    public static class SelectLinqHelper
    {

        #region Filter

        /// <summary>
        /// Фильтрует по GridFilters
        /// <param name="notApply">Если стоит true, то не помечать фильтры</param>
        /// </summary>
        public static IQueryable<TSource> Filter<TSource>(this IQueryable<TSource> source,
            IEnumerable<WhereArgs.Filter> filters, bool notApply, params string[] fields)
        {
            Expression exp = null;
            var param = Expression.Parameter(typeof(TSource), "a");
            if (fields != null && fields.Length == 0) fields = null;

            foreach (var filter in filters.Reverse())
            {
                if (filter.Applied || fields != null && !fields.Contains(filter.Name)) continue;

                var sprop = typeof(TSource).GetPropertyOrField(filter.Name);
                if (sprop == null)
                    throw new Exception("Filter: the property '" + filter.Name + "' is not exist.");
                var type = sprop.ResultType();

                var prop = (sprop is PropertyInfo) ? Expression.Property(param, filter.Name) : Expression.Field(param, filter.Name);

                Expression eq = null;
                switch (filter.Comparison)
                {
                    case Comparison.Eq:
                        switch (filter.FilterType)
                        {
                            case FilterType.List:
                                foreach (var lvalue in ((ReadOnlyCollection<string>)filter.Value).Reverse())
                                {
                                    var eq2 = Expression.Equal(prop, Expression.Constant(lvalue.To(type), type));
                                    eq = eq == null ? eq2 : Expression.OrElse(eq2, eq);
                                }
                                break;
                            case FilterType.String:
                                var s = filter.Value as string;
                                // ReSharper disable PossibleNullReferenceException
                                if (s.isfull() && (s = s.Trim().ToLower()).isfull())
                                // ReSharper restore PossibleNullReferenceException
                                {
                                    MethodInfo like;
                                    if (s[0] == '*' || s[0] == '%')
                                    {
                                        s = s.Substring(1);
                                        like = typeof(string).GetMethod("Contains", new[] { typeof(string) });
                                    }
                                    else
                                        like = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });

                                    var toLower = typeof(string).GetMethod("ToLower", new Type[] { });
                                    eq = Expression.Call(Expression.Call(prop, toLower), like, Expression.Constant(s));
                                }
                                break;
                            default:
                                eq = Expression.Equal(prop, Expression.Constant(filter.Value.To(type), type));
                                break;
                        }
                        break;
                    case Comparison.Gt:
                        eq = Expression.GreaterThanOrEqual(prop, Expression.Constant(filter.Value.To(type), type));
                        break;
                    case Comparison.Lt:
                        eq = Expression.LessThanOrEqual(prop, Expression.Constant(filter.Value.To(type), type));
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }

                if (eq != null)
                {
                    exp = exp == null ? eq : Expression.AndAlso(eq, exp);
                }

                filter.Applied = !notApply;
            }

            return exp == null ? source : source.Where(Expression.Lambda<Func<TSource, bool>>(exp, param));
        }

        /// <summary>
        /// Фильтрует по GridFilters
        /// </summary>
        public static IQueryable<TSource> Filter<TSource>(this IQueryable<TSource> source, WhereArgs e, params string[] fields)
        {
            return e.Filters == null || e.KeyValues != null ? source : source.Filter(e.Filters, false, fields);
        }
        public static IQueryable<TSource> Filter<TSource>(this IEnumerable<TSource> source, WhereArgs e, params string[] fields)
        {
            return source.AsQueryable().Filter(e, fields);
        }

        /// <summary>
        /// Фильтрует по GridFilters
        /// </summary>
        public static IQueryable<TSource> FilterWithoutApply<TSource>(this IQueryable<TSource> source, WhereArgs e, params string[] fields)
        {
            return e.Filters == null || e.KeyValues != null ? source : source.Filter(e.Filters, true, fields);
        }
        public static IQueryable<TSource> FilterWithoutApply<TSource>(this IEnumerable<TSource> source, WhereArgs e, params string[] fields)
        {
            return source.AsQueryable().FilterWithoutApply(e, fields);
        }

        #endregion


        #region Sort

        public static IQueryable<TSource> Sort<TSource>(this IQueryable<TSource> source,
            IEnumerable<WhereArgSortField> sortFields, bool useOrderThen)
        {
            if (sortFields == null) return source;

            foreach (var field in sortFields)
            {
                source = source.Sort(field.Name, field.Dir == SortDirection.ASC, useOrderThen);
                useOrderThen = true;
            }

            return source;
        }

        public static IEnumerable<TSource> Sort<TSource>(this IEnumerable<TSource> source,
            IEnumerable<WhereArgSortField> sortFields, bool useOrderThen)
        {
            if (sortFields == null) return source;

            foreach (var field in sortFields)
            {
                source = source.Sort(field.Name, field.Dir == SortDirection.ASC, useOrderThen);
                useOrderThen = true;
            }

            return source;
        }


        public static IQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, WhereArgs e)
        {
            return source.Sort(e.SortFields, e.UseOrderAsThen);
        }
        public static IQueryable<TSource> ThenBy<TSource>(this IQueryable<TSource> source, WhereArgs e)
        {
            return source.Sort(e.SortFields, true);
        }

        #endregion


        #region Limit

        public static IQueryable<TSource> Limit<TSource>(this IQueryable<TSource> source, WhereArgs e)
        {
            return source.Limit(e.Start, e.Limit);
        }

        #endregion


        #region Where

        public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, WhereArgs e)
        {
            if (e.KeyValues != null)
                source = source.Filter(e.KeyValues);
            else
            {
                if (e.UseFilters)
                    source = source.Filter(e);
                e.Total = source.Count();
            }
            if (e.SortFields.Count > 0)
                source = source.OrderBy(e);

            return source.Limit(e);
        }

        public static IQueryable<TSource> Where<TSource>(this IOrderedQueryable<TSource> source, WhereArgs e)
        {
            e.UseOrderAsThen = true;
            return ((IQueryable<TSource>)source).Where(e);
        }

        public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, WhereArgs e, Action before)
        {
            if (before != null) before();
            return source.Where(e);
        }

        public static IQueryable<TSource> Where<TSource>(this IEnumerable<TSource> source, WhereArgs e)
        {
            return source.AsQueryable().Where(e);
        }
        public static IQueryable<TSource> Where<TSource>(this IEnumerable<TSource> source, WhereArgs e, Action before)
        {
            return source.AsQueryable().Where(e, before);
        }

        #endregion
    }
}